#include "music.h"

static long long int last_mili = 0;

float notes_frequency[] = { 16.35, 17.32, 18.35, 19.45, 20.6, 21.83, 23.12, 24.5, 25.96, 27.5, 29.14, 30.87,
	32.7, 34.65, 36.71, 38.89, 41.2, 43.65, 46.25, 49, 51.91, 55, 58.27, 61.74,
	65.41, 69.3, 73.42, 77.78, 82.41, 87.31, 92.5, 98, 103.83, 110, 116.54, 123.47,
	130.81, 138.59, 146.83, 155.56, 164.81, 174.61, 185, 196, 207.65, 220, 233.08, 246.94,
	261.63, 277.18, 293.66, 311.13, 329.63, 349.23, 369.99, 392, 415.3, 440, 466.16, 493.88,
	523.25, 554.37, 587.33, 622.25, 659.26, 698.46, 739.99, 783.99, 830.61, 880, 932.33, 987.77,
	1046.5, 1108.73, 1174.66, 1244.51, 1318.51, 1396.91, 1479.98, 1567.98, 1661.22, 1760, 1864.66, 1975.53,
	2093, 2217.46, 2349.32, 2489.02, 2637.02, 2793.83, 2959.96, 3135.96, 3322.44, 3520, 3729.31, 3951.07,
	4186.01, 4434.92, 4698.64, 4978.03};
	
	
// Divisores a usar para obter as diferentes notas (sincronizado com o array NotasP).
static int divider[] = { 72977, 68890, 65023, 61346, 57921, 54658, 51608, 48701, 45962, 43388, 40946, 38652,
		  	36489, 34435, 32503, 30681, 28961, 27335, 25799, 24351, 22986, 21694, 20477, 19326,
		  	18242, 17218, 16251, 15340, 14479, 13666, 12899, 12175, 11492, 10847, 10238, 9664,
		  	9121, 8609, 8126, 7670, 7240, 6833, 6450, 6088, 5746, 5424, 5119, 4832,
		  	4561, 4305, 4063, 3835, 3620, 3417, 3225, 3044, 2873, 2712, 2560, 2416,
		  	2280, 2152, 2032, 1918, 1810, 1708, 1612, 1522, 1437, 1356, 1280, 1208,
		  	1140, 1076, 1016, 959, 905, 854, 806, 761, 718, 678, 640, 604,
		  	570, 538, 508, 479, 452, 427, 403, 380, 359, 339, 320, 302,
		  	285, 269, 254, 240, 0};
	
const int music1[][2] = { {Do4, 1}, {Do4, 1}, {Sol4, 1}, {Sol4, 1}, {La4, 1}, {La4, 1}, {Sol4, 2},
			     {Fa4, 1}, {Fa4, 1}, {Mi4, 1}, {Mi4, 1}, {Re4, 1}, {Re4, 1}, {Do4, 2},
			     {Sol4, 1}, {Sol4, 1}, {Fa4, 1}, {Fa4, 1}, {Mi4, 1}, {Mi4, 1}, {Re4, 2},
			     {Sol4, 1}, {Sol4, 1}, {Fa4, 1}, {Fa4, 1}, {Mi4, 1}, {Mi4, 1}, {Re4, 2},
			     {Do4, 1}, {Do4, 1}, {Sol4, 1}, {Sol4, 1}, {La4, 1}, {La4, 1}, {Sol4, 2},
			     {Fa4, 1}, {Fa4, 1}, {Mi4, 1}, {Mi4, 1}, {Re4, 1}, {Re4, 1}, {Do4, 2},
			     {Pause, 4}	
			     };

const int num_notes_music1 = sizeof(music1) / (sizeof(int) * 2);

const int music2[][2] = { {Mi4, 1}, {Re4, 1}, {Do4, 1}, {Re4, 1}, {Mi4, 1}, {Mi4, 1},  {Mi4, 2},
				{Re4, 1}, {Re4, 1}, {Re4, 2}, {Mi4, 1}, {Sol4, 1}, {Sol4, 2},
				{Mi4, 1}, {Re4, 1}, {Do4, 1}, {Re4, 1}, {Mi4, 1}, {Mi4, 1},  {Mi4, 1},
				{Mi4, 1}, {Re4, 1}, {Re4, 1}, {Mi4, 1}, {Re4, 1}, {Do4, 4},
				{Pause, 4}
				};

const int num_notes_music2 = sizeof(music2) / (sizeof(int) * 2);


const int music3[][2] = { {La5, 1}, {Re5, 1}, {Re5, 1}, {Mi5, 1},
				{Fa5, 1}, {Re5, 1}, {Re5, 1}, {Fa5, 1},
				{Mi5, 1}, {Do5, 1}, {Do5, 1}, {Mi5, 1},
				{Fa5, 1}, {Re5, 1}, {Re5, 2},
				{La5, 1}, {Re5, 1}, {Re5, 1}, {Mi5, 1},
				{Fa5, 1}, {Re5, 1}, {Re5, 1}, {Fa5, 1},
				{Mi5, 1}, {Do5, 1}, {Do5, 1}, {Mi5, 1},
				{Fa5, 1}, {Re5, 1}, {Re5, 2},
				{La5, 1}, {La5, 1}, {Sol5, 1}, {Fa5, 1},
				{Mi5, 1}, {Do5, 1}, {Do5, 1}, {Mi5, 1},
				{Sol5, 1}, {Sol5, 1}, {Fa5, 1}, {Mi5, 1},
				{Fa5, 1}, {Re5, 1}, {Re5, 2},
				{La5, 1}, {La5, 1}, {Sol5, 1}, {Fa5, 1},
				{Mi5, 1}, {Do5, 1}, {Do5, 1}, {Mi5, 1},
				{Sol5, 1}, {Sol5, 1}, {Fa5, 1}, {Mi5, 1},
				{Re5, 4}, {Pause, 4}
				};

const int num_notes_music3 = sizeof(music3) / (sizeof(int) * 2);
	

/** Turns the speaker on
 */
void speaker_on()
{
	//outportb(SPEAKER_CTRL, TIMER2_ENABLE);
	unsigned char valor = inportb(SPEAKER_CTRL);
	outportb(SPEAKER_CTRL, valor | SPEAKER_ENABLE | TIMER2_ENABLE);
}

/** Turns the speaker off
 */
void speaker_off()
{
	unsigned char valor = inportb(SPEAKER_CTRL);
	outportb(SPEAKER_CTRL, valor & ~SPEAKER_ENABLE);
	outportb(SPEAKER_CTRL, ~TIMER2_ENABLE);	
}

void play_note(Note *note)
{
	timer_load(TIMER_2, note->freq);
	
	speaker_on();
	mili_sleep(note->dur);
	speaker_off();
}

void play_song(Song *s)
{
	int i = 0;
	
	for(; i < s->lenght; i++)
	{
		play_note(s->notes+i);
		mili_sleep(s->pause);
	}
}

Note __nota = {1000, 5};

void beep(void)
{
	play_note(&__nota);
}

Music* create_music(const char* name, const int notes[][2], const int num_notes, const int pause, const int time)
{
	int i;

	Music* music = (Music*)malloc(sizeof(Music));
	music->name = strdup(name);
	music->num_notes = num_notes;
	music->notes = (int**)malloc(sizeof(int) * music->num_notes);
	music->time = time;

	for (i = 0; i < music->num_notes; i++) {
		
		music->notes[i] = (int*)malloc(sizeof(int) * 2);
		music->notes[i][0] = notes[i][0];

		//afecta o tempo da musica
		music->notes[i][1] = notes[i][1] * music->time; 
	}
	
	music->sleep = pause;	//pausa entre as notas

	music->note_i = 0;
	music->note_time = 0;
	music->state = PAUSE;
	music->playing = false;

	return music;
}

void init_music (Music* music)
{
	music->state = PAUSE;
	music->note_i = 0;
	music->note_time = 0;
	music->playing = true;
	last_mili = 0;
}
	

void free_music(Music* music)
{
	int i;
	
	for (i = 0; i < music->num_notes; i++) {
		free(music->notes[i]);
	}		

	free(music->notes);
	free(music->name);
	free(music);
}


void play_music_ints(Music *music)
{
	//actualiza o tempo decorrido da nota
	music->note_time += get_mili_tick() - last_mili;
	last_mili = get_mili_tick();

	switch (music->state) 
	{
		case PLAY:
			if (music->note_time > music->notes[music->note_i][1]) {	//a nota acabou
				music->state = PAUSE;
				music->note_time = 0;
				music->note_i++;
				speaker_off();
			}
			break;
		

		case PAUSE:
			if (music->note_time > music->sleep) {		//acabou a pausa
				music->note_time = 0;
			
				if (music->note_i >= music->num_notes) {	//nao ha mais notas a tocar
					music->state = STOP;
				}
	
				else {
					music->state = PLAY;
					
					//programa o timer com o divisor a usar para a proxima nota
					int valor = (int)divider[music->notes[music->note_i][0]];
					timer_load(2, valor);

					//se nao for uma pausa
					if (valor != 0) {
						speaker_on();
					}
				}
			}
			break;

		case STOP: //coloca a musica no estado inicial
			music->state = PAUSE;
			music->note_i = 0;
			speaker_off();
			break;
		}

}
